/**
 * @author 陈泰来
 * @version V1.0
 * @Project ioex
 * @Title
 * @Description NIO 时间处理器客户端c处理类
 * @Package com.echo.ioex.nio.client
 * @date 2020/9/25 1:21 下午
 * @Copyright: 陈泰来の学习档案 .
 */
package com.echo.ioex.nio.client.handler;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.Set;

@Slf4j
public class TimeClientHandler implements Runnable {

    private String host;
    private int port;
    private Selector selector;
    private SocketChannel socketChannel;
    private volatile boolean stop;

    public TimeClientHandler(String host, int port) {
        this.host = host;
        this.port = port;

        try {
            //创建selector
            selector = Selector.open();
            //打开socket
            socketChannel = SocketChannel.open();
            //设置非阻塞
            socketChannel.configureBlocking(false);
        } catch (Exception e) {
            log.error("处理时间服务器客户端时发生异常");
            System.exit(1);
        }
    }

    @Override
    public void run() {
        try {
            //尝试建立连接
            doConnect();
        } catch (Exception e) {
            //出现异常则退出
            System.exit(1);
        }

        //多路复用器 轮询所有SelectKey
        while (!stop) {
            //每秒 1 次
            try {
                selector.select(1000);
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectionKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    //取出当前key
                    key = it.next();
                    it.remove();

                    handleInput(key);

                }

            } catch (IOException e) {
                //多路复用器轮询key时出现异常直接退出.
                System.exit(1);
            }
        }

        //多路复用器停止工作后，需要调用close方法释放资源。其他注册在上面的channel和pipe进程等资源会由JDK内部自动销毁和释放。
        if (selector != null) {
            try {
                selector.close();
            } catch (IOException e) {
                log.error("关闭多路复用器时出现异常");
            }
        }


    }

    /**
     * @return void[]
     * @author chentl
     * @description 处理客户端 多路复用器轮询出来的可用channel
     * @date 2020/9/25 1:44 下午
     * @params [key]
     */
    public void handleInput(SelectionKey key) throws IOException {
        //key 有效？
        if (key.isValid()) {
            SocketChannel sc = (SocketChannel) key.channel();

            //key 可连接？
            if (key.isConnectable()) {
                //连接完成？
                if (sc.finishConnect()) {
                    //连接完成后，将channel注册到多路复用器上 监听 读事件
                    sc.register(selector, SelectionKey.OP_READ);
                    //写一条查询命令到 NIOTimeServer
                    doWrite(sc);
                }
            }
            //key 有消息可读
            if (key.isReadable()) {
                ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                doRead(sc, readBuffer, key);

            }
        }
    }

    /**
     * @return void
     * @author chentl
     * @description 尝试建立连接
     * @date 2020/9/25 1:27 下午
     * @params []
     */
    private void doConnect() throws IOException {
        //直接连接 注册到多路复用器上
        // 如果成功 发送Time Order消息并应答
        // 如果失败 保持连接尝试
        if (socketChannel.connect(new InetSocketAddress(host, port))) {
            socketChannel.register(selector, SelectionKey.OP_READ);
            doWrite(socketChannel);
        } else socketChannel.register(selector, SelectionKey.OP_CONNECT);
    }


    /**
     * @return void
     * @author chentl
     * @description 应答消息
     * @date 2020/9/25 1:29 下午
     * @params [socketChannel]
     */
    private void doWrite(SocketChannel socketChannel) throws IOException {
        byte[] req = "QUERY TIME ORDER".getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
        writeBuffer.put(req);
        writeBuffer.flip();
        socketChannel.write(writeBuffer);
        if (!writeBuffer.hasRemaining()) {
            System.out.println("发送命令到时间服务器成功!");
        }
    }

    /**
     * @return void
     * @author chentl
     * @description 处理从server读取的消息
     * @date 2020/9/25 1:57 下午
     * @params [byteBuffer]
     */
    private void doRead(SocketChannel sc, ByteBuffer readBuffer, SelectionKey key) throws IOException {
        int readBytes = sc.read(readBuffer);
        if (readBytes > 0) {
            readBuffer.flip();
            byte[] bytes = new byte[readBuffer.remaining()];
            readBuffer.get(bytes);
            String body = new String(bytes, StandardCharsets.UTF_8);
            System.out.println("Now is :" + body);
            //停止轮询
            stop();
        } else if (readBytes < 0) {
            //对端链路关闭
            key.cancel();
            sc.close();
        }

    }

    /**
     * @return void
     * @author chentl
     * @description 停止Selector轮询
     * @date 2020/9/25 1:36 下午
     * @params []
     */
    public void stop() {
        this.stop = true;
    }
}
